//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#include <ddk.h>
#include "ftl.h"
#include "Mutex.h"

#define NO_ERROR 0

IDeviceDriver * CDECL CreateFlash(uint_t uDeviceNo, void *pvParameter);

class BlkFlash : public CDeviceDriver {
public:
    CARAPI Read(
        /* [in] */ Int64 u64Offset,
        /* [in] */ Int32 bytesToRead,
        /* [out] */ MemoryBuf * pData,
        /* [out] */ IEvent * * ppCompletionEvent);

    CARAPI Write(
        /* [in] */ Int64 u64Offset,
        /* [in] */ const MemoryBuf & ebbData,
        /* [out] */ Int32 * pBytesWritten,
        /* [out] */ IEvent * * ppCompletionEvent);

    CARAPI Control(
        /* [in] */ Handle32 controlCode,
        /* [in] */ const MemoryBuf & inBuffer,
        /* [out] */ MemoryBuf * pOutBuffer,
        /* [out] */ IEvent ** ppCompletionEvent);

    ECode DeleteBlock(
        /* [in] */ UInt32 sector,
        /* [in] */ UInt32 nblocks);


    virtual void Dispose();

public:
    ECode Initialize(Address physbase, UInt32 offset, UInt32 size);
    BlkFlash() {}
    ~BlkFlash();
    friend IDeviceDriver * CDECL CreateBlkFlash(uint_t uDeviceNo, void *pvParameter);

private:
    static ECode _MutexInit(IMutex **ppMutex);

    static ECode _MutexLock(IMutex **ppMutex);

    static ECode _MutexUnlock(IMutex **ppMutex);

    partition_t m_fatPart;
};

BlkFlash::~BlkFlash()
{
    if (m_fatPart.mtd)
        (m_fatPart.mtd)->Release();
    if (m_fatPart.Lock)
        (m_fatPart.Lock)->Release();
}

void BlkFlash::Dispose()
{
    delete this;
}

ECode BlkFlash::_MutexInit(IMutex **ppMutex)
{
    if (NULL == ppMutex) {
        return E_INVALID_ARGUMENT;
    }
    *ppMutex = new CMutex();
    if (NULL == *ppMutex) {
        return E_OUT_OF_MEMORY;
    }
    (*ppMutex)->AddRef();
    return NOERROR;
}

ECode BlkFlash::_MutexLock(IMutex **ppMutex)
{
    WaitResult wr;
    return (*ppMutex)->Lock(&wr);
}

ECode BlkFlash::_MutexUnlock(IMutex **ppMutex)
{
    return (*ppMutex)->Unlock();
}

ECode BlkFlash::Read(
        /* [in] */ Int64 u64Offset,
        /* [in] */ Int32 bytesToRead,
        /* [out] */ MemoryBuf * pData,
        /* [out] */ IEvent * * ppCompletionEvent)
{
    if (pData == NULL) {
        return E_INVALID_ARGUMENT;
    }
    Byte *pBuf = pData->GetPayload();
    partition_t *part = &m_fatPart;
    Byte buffer[SECSZ];
    UInt32 sector,offset,leftsize,rightsize;
    int ret,nblocks, sizeRead;

    DBGOUT(DBGFS,1,printf("FtlReadAt: Offset %x, vBuf %x, Size %x\n"
        , (UInt32)u64Offset, (UInt32)pBuf, bytesToRead));
    offset = (UInt32)u64Offset;
    sector = RoundUp(offset, SECSZ) >> 9;
    nblocks = ((offset + bytesToRead)>>9) - sector;
    if (nblocks < 0) nblocks=0;
    rightsize = (offset + bytesToRead) % SECSZ;
    offset = offset % SECSZ;
    leftsize = (offset)?SECSZ - offset:0;
    DBGOUT(DBGFS,0,printf("Ftl: sector=%x, nblocks=%x, offset=%x, leftsize=%x, rightsize=%x\n",
            sector, nblocks, offset, leftsize, rightsize));

    sizeRead = 0;
    ret = NO_ERROR;

    // lock all write routine
    _MutexLock(&part->Lock);

    if (nblocks) {
        if ( (const int)(!(Address)(pBuf + leftsize)) & 3) {
            ret = ftl_read(part, pBuf+leftsize, sector, nblocks);
            if (ret) goto Done;
        }
        else {
            int i;
            for(i=0;i<nblocks;i++) {
                ret = ftl_read(part,buffer,sector+i,1);
                if (ret) goto Done;
                memcpy(pBuf+leftsize+(i<<9),buffer,SECSZ);
            }
        }
        sizeRead += nblocks << 9;
    }
    if (offset) {
        if ((UInt32)bytesToRead < leftsize) {
            leftsize = bytesToRead;
            rightsize = 0;
        }
        ret = ftl_read(part, buffer, sector-1, 1);
        if (ret) goto Done;
        memcpy(pBuf,buffer+offset,leftsize);
        sizeRead += leftsize;

    }
    if (rightsize) {
        ret = ftl_read(part, buffer, sector+nblocks, 1);
        if (ret) goto Done;
        memcpy(pBuf+bytesToRead-rightsize, buffer, rightsize);
        sizeRead += rightsize;
    }

Done:
    _MutexUnlock(&part->Lock);
    if (!ret) {
        pData->SetUsed(sizeRead);
        return NOERROR;
    }
    else
        return E_IO;
}

ECode BlkFlash::Write(
        /* [in] */ Int64 u64Offset,
        /* [in] */ const MemoryBuf & ebbData,
        /* [out] */ Int32 * pBytesWritten,
        /* [out] */ IEvent * * ppCompletionEvent)
{
    partition_t *part = &m_fatPart;
//    erase_unit_header_t *hdr = &part->header;
    Byte buffer[SECSZ];
    UInt32 sector,offset,leftsize,rightsize, sizeToWrite, len;
    int ret,nblocks;
    Byte *pBuf = ebbData.GetPayload();

    DBGOUT(DBGFS,1,printf("FtlWriteAt: u64Offset %x, ebbData %x, pBytesWritten %x\n", (UInt32)u64Offset, (UInt32)pBuf, pBytesWritten));

    offset = (UInt32)u64Offset;
    sizeToWrite = (UInt32)ebbData.GetUsed();
    sector = RoundUp(offset, SECSZ) >> 9;
    nblocks = ((offset + sizeToWrite)>>9) - sector;
    if (nblocks < 0) nblocks=0;
    rightsize = (offset + sizeToWrite) % SECSZ;
    offset = offset % SECSZ;
    leftsize = (offset)?SECSZ - offset:0;
    DBGOUT(DBGFS,0,printf("Ftl: sector=%x, nblocks=%x, offset=%x, leftsize=%x, rightsize=%x\n",
            sector, nblocks, offset, leftsize, rightsize));

    len = 0;
    ret = NO_ERROR;

    // lock all write routine
    _MutexLock(&part->Lock);

    if (nblocks) {

        if ( (const int)(!(Address)(pBuf + leftsize)) & 3) {

            ret = ftl_write(part, (Byte*)(pBuf+leftsize), sector, nblocks);
            if (ret) goto Done;
        }
        else {
            int i;
            for(i=0;i<nblocks;i++) {
                memcpy(buffer,pBuf+leftsize+(i<<9),SECSZ);
                ret = ftl_write(part,buffer,sector+i,1);
                if (ret) goto Done;
            }
        }
        len += nblocks << 9;
    }
    if (offset) {
        if (sizeToWrite < leftsize) {
            leftsize = sizeToWrite;
            rightsize = 0;
        }
        ret = ftl_read(part, buffer, sector-1, 1);
        if (ret) goto Done;
        memcpy(buffer+offset,pBuf,leftsize);
        ret = ftl_write(part, buffer, sector-1, 1);
        if (ret) goto Done;

        len += leftsize;

    }
    if (rightsize) {
        ret = ftl_read(part, buffer, sector+nblocks, 1);
        if (ret) goto Done;
        memcpy(buffer, pBuf+sizeToWrite-rightsize, rightsize);
        ret = ftl_write(part, buffer, sector+nblocks, 1);
        if (ret) goto Done;
        len += rightsize;
    }

Done:
    _MutexUnlock(&part->Lock);
    if (!ret) {
        if (pBytesWritten)
            *pBytesWritten = len;
        return NOERROR;
    }
    else {
        return E_IO;
    }
}

ECode BlkFlash::Control(
        /* [in] */ Handle32 controlCode,
        /* [in] */ const MemoryBuf & inBuffer,
        /* [out] */ MemoryBuf * pOutBuffer,
        /* [out] */ IEvent * * ppCompletionEvent)
{
    assert(m_fatPart.mtd != NULL);
    if(controlCode == FTLControl_DeleteBlock) {
        UInt32 sector=0;
        UInt32 nblocks=0;
        memcpy((char *)&sector, inBuffer.GetPayload(), 4);
        memcpy((char *)&nblocks, (char *)inBuffer.GetPayload() + 4, 4);
        return DeleteBlock(sector, nblocks);
    }
    return (m_fatPart.mtd)->Control(controlCode, inBuffer, pOutBuffer
        ,ppCompletionEvent);
}

ECode BlkFlash::DeleteBlock(
        /* [in] */ UInt32 sector,
        /* [in] */ UInt32 nblocks)
{
    int ret;
    _MutexLock(&m_fatPart.Lock);
    ret = ftl_delete(&m_fatPart, sector, nblocks);
    _MutexUnlock(&m_fatPart.Lock);

    if (ret)
        return E_IO;
    else
        return NOERROR;
}

EXTERN IDeviceDriver * CDECL CreateBlkFlash(uint_t uDeviceNo, void *pvParameter)
{
    ECode ec;
    BlkFlash *pBlkFlash = new BlkFlash();
    if (NULL == pBlkFlash)        return NULL;
    IDeviceDriver *pFlash = NULL;
    partition_t *part;
    DBGOUT(DBGFS,1,printf("In CreateBlkFalsh before CreateFlash\n"));

    pFlash = CreateFlash(DEVICENO_NONE, NULL);
    if (NULL == pFlash) {
        printf("*ERROR* failed to find flash\n");
        goto ErrorExit;
    }
    DBGOUT(DBGFS,1,printf("In CreateBlkFalsh before Open Device\n"));
    // Open
    ec = pFlash->Control(
            FlashControl_Open,
            NULL_MEMORYBUF,
            NULL,
            NULL);
    if (FAILED(ec)) {
        printf("*ERROR* failed to open flash\n");
        goto ErrorExit;
    }
    DBGOUT(DBGFS,1,printf("In CreateFalsh after open device\n"));
    part = &(pBlkFlash->m_fatPart);
    // maybe alloc new part
    memset(part,0,sizeof(partition_t));

    BlkFlash::_MutexInit(&part->Lock);
    DBGOUT(DBGFS,1,printf("In CreateFalsh after mutexInit()\n"));

    printf("mtd %x\n", pFlash);

    // check part
    if (ftl_init(part,pFlash) != 0) goto ErrorExit;

    pBlkFlash->AddRef();
    return pBlkFlash;

ErrorExit:
    if (pFlash) {
        pFlash->Release();
        if (pBlkFlash && ((pBlkFlash->m_fatPart).mtd)) {
            (pBlkFlash->m_fatPart).mtd = NULL;
        }
    }
    if (pBlkFlash)
        delete pBlkFlash;
    return NULL;
}
